public class part3 {
	int[] x, y0, y1;
	int[][] A0, A1;
	String[] y;

	public part3(int[] input) {
		x = modifyX(input);
		y0 = new int[x.length];
		y1 = new int[x.length];
		y = new String[x.length];
		A0 = new int[x.length][x.length];
		A1 = new int[x.length][x.length];
	}

	private int[] modifyX(int[] matrix) {
		if (matrix.length % 8 == 0)
			return matrix;
		else {
			int[] newX = new int[matrix.length + (8 - (matrix.length % 8))];
			for (int i = 0; i < newX.length; i++) {
				if (i < matrix.length)
					newX[i] = matrix[i];
				else
					newX[i] = 0;
			}

			return newX;
		}
	}

	public void encode() {
		if (x != null) {
			createY0Y1();
			for (int i = 0; i < x.length; i++) {
				String y_0 = Integer.toString(y0[i]);
				String y_1 = Integer.toString(y1[i]);
				y[i] = y_0 + y_1;
			}
			createA();
		}
	}

	public void createY0Y1() {
		for (int i = 0; i < x.length; i++) {
			y0[i] = x[i];
			y1[i] = x[i];

			if (i - 1 < 0)
				y1[i] += 0;
			else
				y1[i] += x[i - 1];

			if (i - 2 < 0)
				y0[i] += 0;
			else
				y0[i] += x[i - 2];

			if (i - 3 < 0) {
				y0[i] += 0;
				y1[i] += 0;
			} else {
				y0[i] += x[i - 3];
				y1[i] += x[i - 3];
			}

			if (y0[i] % 2 == 0)
				y0[i] = 0;
			else
				y0[i] = 1;

			if (y1[i] % 2 == 0)
				y1[i] = 0;
			else
				y1[i] = 1;
		}
	}

	private void createA() {
		for (int i = 0; i < A0.length; i++) {
			A0[i][i] = 1;
			A1[i][i] = 1;

			if (i - 1 >= 0)
				A1[i][i - 1] = 1;

			if (i - 2 >= 0)
				A0[i][i - 2] = 1;

			if (i - 3 >= 0) {
				A0[i][i - 3] = 1;
				A1[i][i - 3] = 1;
			}
		}
	}

	public double[][] decodeJacobi(int[][] Aprime, int[] yprime) {
		double[][] xdouble = new double[x.length][1]; // decoding matrix

		int[][] S = new int[Aprime.length][Aprime.length]; // The diagonal
															// matrix of A
		for (int i = 0; i < S.length; i++)
			S[i][i] = Aprime[i][i];

		double[][] T = minus(S, Aprime); // T is the subtraction of S from A

		double[][] bprime = new double[yprime.length][1]; // converts y array
															// into b, a
															// y.length by 1
															// matrix
		for (int i = 0; i < yprime.length; i++)
			bprime[i][0] = (double) yprime[i];

		for (int n = 0; n < 24; n++) {
			double[][] Tg = multiply(T, xdouble); // Creates a matrix of T times
													// the decoding matrix
			for (int i = 0; i < xdouble.length; i++)
				xdouble[i][0] = (double) (Tg[i][0] + bprime[i][0])
						/ (double) S[i][i]; // The element in the decoding
											// matrix corresponds to the element
											// in the Tg matrix plus the b over
											// diagonal
		}

		System.out.println("Before format: ");
		printMatrix(xdouble);

		xdouble = formatDecode(xdouble);

		System.out.println("After format: ");
		printMatrix(xdouble);

		return xdouble;
	}

	public double[][] decodeGaussSeidel(int[][] Aprime, int[] yprime) {
		double[][] xdouble = new double[x.length][1]; // decoding matrix

		double[][] D = new double[Aprime.length][Aprime.length]; // diagonal
		for (int i = 0; i < D.length; i++)
			D[i][i] = (double) Aprime[i][i];

		double[][] Dinverse = new double[D.length][D.length]; // the inverse of
																// the diagonal
		for (int i = 0; i < D.length; i++)
			Dinverse[i][i] = (double) (1 / D[i][i]);

		double[][] U = new double[Aprime.length][Aprime.length]; // upper
																	// triangle
																	// of zeros
		for (int i = 0; i < U.length; i++)
			for (int j = 0; j < U[i].length; j++) {
				if (j == i)
					break;
				else
					U[i][j] = Aprime[i][j];
			}

		double[][] L = new double[Aprime.length][Aprime.length]; // lower
																	// triangle
																	// of zeros
		for (int i = 0; i < L.length; i++)
			for (int j = i + 1; j < L[i].length; j++)
				L[i][j] = Aprime[i][j];

		double[][] bprime = new double[yprime.length][1]; // converts y array
															// into b, a
															// y.length by 1
															// matrix
		for (int i = 0; i < yprime.length; i++)
			bprime[i][0] = (double) yprime[i];

		for (int n = 0; n < 24; n++) {
			double[][] a = multiply(U, xdouble); // multiply upper triangle of
													// zeros and the decoding
													// matrix
			double[][] b = multiply(L, xdouble); // multiply lower triangle of
													// zeros and the decoding
													// matrix
			double[][] c = minus(bprime, b); // subtract the multiplication of
												// the lower triangle zeros and
												// decoding matrix from b
			double[][] d = minus(c, a); // subtract the multiplication of the
										// upper triangle of zeros and decoding
										// matrix from the last step
			double[][] e = multiply(Dinverse, d); // multiply the inverse of the
													// diagonal matrix with the
													// last step
			xdouble = e; // set the decode matrix to the new matrix found
		}

		System.out.println("Before format: ");
		printMatrix(xdouble);

		xdouble = formatDecode(xdouble);

		System.out.println("After format: ");
		printMatrix(xdouble);

		return xdouble;
	}

	private double[][] formatDecode(double[][] xdouble) {
		double[][] ans = new double[xdouble.length][1];

		for (int i = 0; i < ans.length; i++)
			ans[i][0] = Math.abs(xdouble[i][0]) % 2;

		return ans;
	}

	public int[] getX() {
		return x;
	}

	public int[] getY0() {
		return y0;
	}

	public int[] getY1() {
		return y1;
	}

	public int[][] getA0() {
		return A0;
	}

	public int[][] getA1() {
		return A1;
	}

	public String[] getY() {
		return y;
	}

	public double[][] multiply(double[][] a, double[][] b) {
		double[][] ans = new double[a.length][b[0].length];

		for (int i = 0; i < ans.length; i++)
			for (int j = 0; j < ans[i].length; j++)
				for (int k = 0; k < a[0].length; k++)
					ans[i][j] += (a[i][k] * b[k][j]);

		return ans;
	}

	public int[][] multiply(int[][] a, int[][] b) {
		int[][] ans = new int[a.length][b[0].length];

		for (int i = 0; i < ans.length; i++)
			for (int j = 0; j < ans[i].length; j++)
				for (int k = 0; k < a[0].length; k++)
					ans[i][j] += (a[i][k] * b[k][j]);

		return ans;
	}

	public double[][] minus(int[][] a, int[][] b) {
		double[][] ans = new double[a.length][b.length];

		for (int i = 0; i < ans.length; i++)
			for (int j = 0; j < ans[i].length; j++)
				ans[i][j] = (double) (a[i][j] - b[i][j]);

		return ans;
	}

	public double[][] minus(double[][] a, double[][] b) {
		double[][] ans = new double[a.length][b[0].length];

		for (int i = 0; i < ans.length; i++)
			for (int j = 0; j < ans[i].length; j++)
				ans[i][j] = a[i][j] - b[i][j];

		return ans;
	}

	public void printArray(int[] arr) {
		if (arr.length > 0) {
			System.out.print("[ " + arr[0]);
			for (int i = 1; i < arr.length; i++)
				System.out.print(", " + arr[i]);
		} else
			System.out.println("[ ");

		System.out.println(" ]");
	}

	public void printArray(String[] arr) {
		if (arr.length > 0) {
			System.out.print("[ " + arr[0]);
			for (int i = 1; i < arr.length; i++)
				System.out.print(", " + arr[i]);
		} else
			System.out.println("[ ");

		System.out.println(" ]");
	}

	public void printMatrix(int[][] arr) {
		if (arr.length > 0) {
			for (int i = 0; i < arr.length; i++) {
				System.out.print("[ " + arr[i][0]);
				for (int j = 1; j < arr[i].length; j++)
					System.out.print(", " + arr[i][j]);

				System.out.println(" ]");
			}
		} else
			System.out.println("[ ]");
	}

	public void printMatrix(double[][] arr) {
		if (arr.length > 0) {
			for (int i = 0; i < arr.length; i++) {
				System.out.print("[ " + arr[i][0]);
				for (int j = 1; j < arr[i].length; j++)
					System.out.print(", " + arr[i][j]);

				System.out.println(" ]");
			}
		} else
			System.out.println("[ ]");
	}

	public static void main(String[] args) {
		int[] input = { 1, 0, 1, 1, 0 };
		part3 cc = new part3(input);
		cc.encode();
		System.out.println("X: ");
		cc.printArray(cc.getX());
		System.out.println("Y0: ");
		cc.printArray(cc.getY0());
		System.out.println("Y1: ");
		cc.printArray(cc.getY1());
		System.out.println("Y: ");
		cc.printArray(cc.getY());
		System.out.println("Jacobi with A0 and Y0: ");
		double[][] jacobi1 = cc.decodeJacobi(cc.getA0(), cc.getY0());
		System.out.println("Jacobi with A1 and Y1: ");
		double[][] jacobi2 = cc.decodeJacobi(cc.getA1(), cc.getY1());
		System.out.println("Gauss-Seidel with A0 and Y0: ");
		double[][] guass1 = cc.decodeGaussSeidel(cc.getA0(), cc.getY0());
		System.out.println("Gauss-Seidel with A1 and Y1: ");
		double[][] guass2 = cc.decodeGaussSeidel(cc.getA1(), cc.getY1());

		
		 int[] input2 = { 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0,
		 1, 0, 1, 0 };
		 cc = new part3(input2);
		 cc.encode();
		 System.out.println("X: ");
		 cc.printArray(cc.getX());
		 System.out.println("Y0: ");
		 cc.printArray(cc.getY0());
		 System.out.println("Y1: ");
		 cc.printArray(cc.getY1());
		 System.out.println("Y: ");
		 cc.printArray(cc.getY());
		 System.out.println("Jacobi with A0 and Y0: ");
		 jacobi1 = cc.decodeJacobi(cc.getA0(), cc.getY0());
		 System.out.println("Jacobi with A1 and Y1: ");
		 jacobi2 = cc.decodeJacobi(cc.getA1(), cc.getY1());
		 System.out.println("Gauss-Seidel with A0 and Y0: ");
		 guass1 = cc.decodeGaussSeidel(cc.getA0(), cc.getY0());
		 System.out.println("Gauss-Seidel with A1 and Y1: ");
		 guass2 = cc.decodeGaussSeidel(cc.getA1(), cc.getY1());
		
		int[] input3 = { 0, 1, 1, 1, 0, 0, 1, 1, 0, 1 };
		cc = new part3(input3);
		cc.encode();
		System.out.println("X: ");
		cc.printArray(cc.getX());
		System.out.println("Y0: ");
		cc.printArray(cc.getY0());
		System.out.println("Y1: ");
		cc.printArray(cc.getY1());
		System.out.println("Y: ");
		cc.printArray(cc.getY());
		System.out.println("Jacobi with A0 and Y0: ");
		jacobi1 = cc.decodeJacobi(cc.getA0(), cc.getY0());
		System.out.println("Jacobi with A1 and Y1: ");
		jacobi2 = cc.decodeJacobi(cc.getA1(), cc.getY1());
		System.out.println("Gauss-Seidel with A0 and Y0: ");
		guass1 = cc.decodeGaussSeidel(cc.getA0(), cc.getY0());
		System.out.println("Gauss-Seidel with A1 and Y1: ");
		guass2 = cc.decodeGaussSeidel(cc.getA1(), cc.getY1());

		int[] input4 = { 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1 };
		cc = new part3(input4);
		cc.encode();
		System.out.println("X: ");
		cc.printArray(cc.getX());
		System.out.println("Y0: ");
		cc.printArray(cc.getY0());
		System.out.println("Y1: ");
		cc.printArray(cc.getY1());
		System.out.println("Y: ");
		cc.printArray(cc.getY());
		System.out.println("Jacobi with A0 and Y0: ");
		jacobi1 = cc.decodeJacobi(cc.getA0(), cc.getY0());
		System.out.println("Jacobi with A1 and Y1: ");
		jacobi2 = cc.decodeJacobi(cc.getA1(), cc.getY1());
		System.out.println("Gauss-Seidel with A0 and Y0: ");
		guass1 = cc.decodeGaussSeidel(cc.getA0(), cc.getY0());
		System.out.println("Gauss-Seidel with A1 and Y1: ");
		guass2 = cc.decodeGaussSeidel(cc.getA1(), cc.getY1());

		int[] input5 = { 0, 1, 0, 1 };
		cc = new part3(input5);
		cc.encode();
		System.out.println("X: ");
		cc.printArray(cc.getX());
		System.out.println("Y0: ");
		cc.printArray(cc.getY0());
		System.out.println("Y1: ");
		cc.printArray(cc.getY1());
		System.out.println("Y: ");
		cc.printArray(cc.getY());
		System.out.println("Jacobi with A0 and Y0: ");
		jacobi1 = cc.decodeJacobi(cc.getA0(), cc.getY0());
		System.out.println("Jacobi with A1 and Y1: ");
		jacobi2 = cc.decodeJacobi(cc.getA1(), cc.getY1());
		System.out.println("Gauss-Seidel with A0 and Y0: ");
		guass1 = cc.decodeGaussSeidel(cc.getA0(), cc.getY0());
		System.out.println("Gauss-Seidel with A1 and Y1: ");
		guass2 = cc.decodeGaussSeidel(cc.getA1(), cc.getY1());
	}

}
